package org.ainlolcat.ainscience.project;

import org.ainlolcat.Variable;
import org.ainlolcat.ainscience.Controller;
import org.ainlolcat.ainscience.entity.Entity;
import org.ainlolcat.ainscience.entity.EntityFactory;
import org.ainlolcat.ainscience.entity.UnsupportedEntityTypeException;
import org.ainlolcat.ainscience.metaentity.MetaEntity;
import org.ainlolcat.ainscience.metaentity.MetaEntityFactory;
import org.ainlolcat.ainscience.metaentity.UnsupportedMetaEntityTypeException;
import org.ainlolcat.ainscience.templates.Template;
import org.ainlolcat.ainscience.templates.TemplateFactory;
import org.ainlolcat.ctp.util.IDGenerator;
import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author ainlolcat
 * Date: 10/26/11
 */
public class ProjectFileHandler {
    private static Logger log = Logger.getLogger(ProjectFileHandler.class);

    public static String TEMPLATE_TAG = "template";
    public static String TEMPLATE_ATTR_NAME = "name";
    public static String TEMPLATE_ATTR_FILE = "file";

    public static String META_ENTITY_TAG = "metaentity";
    public static String META_ENTITY_PATH_TAG = "path";

    public static String ENTITY_TAG = "entity";
    public static String ENTITY_ATTR_ID = "id";
    public static String ENTITY_ATTR_NAME = "name";
    public static String ENTITY_ATTR_FILE = "file";
    public static String ENTITY_ATTR_TEMPLATE_NAME = "template";
    public static String ENTITY_ATTR_MATRIX_NUM = "matrixNum";
    public static String ENTITY_ATTR_ENTITY_TYPE = "entityType";
    public static String LINK_TAG = "link";
    public static String LINK_ATTR_NAME = "name";
    public static String LINK_ATTR_COLUMN = "column";
    public static String LINK_ATTR_L2ENTITY = "l2entity";
    public static String LINK_ATTR_L2COLUMN = "l2column";
    public static String LINK_ATTR_TYPE = "type";


    /**
     * loadTemplates project by file definition
     * @param filename   filename which contain definition (xml)
     * @param answer     project which will be modified
     * @return           result project
     * @throws JDOMException
     * @throws IOException
     */
    public Project loadTemplates(String filename, Project answer) throws JDOMException, IOException {
        File project = new File(filename);
        Document doc = new SAXBuilder().build(project);
        String homepath = project.getParentFile().getCanonicalPath();
        answer = loadVariables(answer, doc, homepath);
        //templates don't have any dependencies so it's simple
        answer = loadTemplates(answer,doc,homepath);
        //loadTemplates meta entities
        answer = loadMetaEntities(answer, doc, homepath);
        //loadTemplates entities
        answer = loadEntities(answer,doc,homepath);
        return answer;
    }

    public Project loadVariables(Project answer, Document doc, String homepath){
        List<Element> variables = doc.getRootElement().getChildren("var");
        if (variables!=null){
            for (Element var: variables){
                answer.addVariable(loadVariable(var));
            }
        }
        return answer;
    }

    private Variable loadVariable(Element element){
        String name = element.getAttributeValue("name");
        String type = element.getAttributeValue("type");
        String value = element.getAttributeValue("value");
        return new Variable(name, value,  Variable.resolveType(type));
    }

    /**
     * loadTemplates project by file definition
     * @param filename   filename which contain definition (xml)
     * @return           result project
     * @throws JDOMException
     * @throws IOException
     */
    public void loadEntities(String filename) throws JDOMException, IOException {
        File project = new File(filename);
        Document doc = new SAXBuilder().build(project);
        String homepath = project.getParentFile().getCanonicalPath();
        //loadTemplates templates.
        //templates don't have any dependencies so it's simple
        //answer = loadTemplates(answer,doc,homepath);
        //loadTemplates meta entities
        loadMetaEntities(doc, homepath);
        //loadTemplates entities
        loadEntities(doc,homepath);

    }

    
    /**
     * add templates from project definition (Document) to project
     * @param answer -      project which will be changed and filled with new templates
     * @param doc -         JDom Document which contain definition for templates
     * @param homepath -    path to project file. Uses in relative paths
     * @return              modified project
     * @throws JDOMException
     * @throws IOException
     */
    private Project loadTemplates(Project answer, Document doc, String homepath) throws JDOMException, IOException {
        List<org.jdom.Element> templates =  doc.getRootElement().getChildren(TEMPLATE_TAG);
        for (Element e:templates){
            try{
                String name = e.getAttributeValue(TEMPLATE_ATTR_NAME);
                String file = e.getAttributeValue(TEMPLATE_ATTR_FILE);
                Template template = TemplateFactory.parse(file.replace("%HOME%", homepath));
                template.setId(IDGenerator.getNextIntegerID());
                template.setName(name);
                answer.addTemplate(template);
            }catch(Exception ez){
            ez.printStackTrace();
            }
        }
        return answer;
    }

    /**
     * loadTemplates entities from project definition
     * @param answer -      project which will be changed and filled with new templates
     * @param doc -         JDom Document which contain definition for entities
     * @param homepath -    path to project file. Uses in relative paths
     * @return
     * @throws IOException
     */
    private Project loadEntities(Project answer, Document doc, String homepath) throws IOException {
        List<org.jdom.Element> entities =  doc.getRootElement().getChildren("entity");
        for (Element e:entities){
            try{
                Entity entity = new EntityFactory().load(e);
                answer.addEntity(entity);
            }catch(Exception ez){
                ez.printStackTrace();
            }
        }
        return answer;
    }

    /**
     * loadTemplates entities from project definition
     * @param doc -         JDom Document which contain definition for entities
     * @param homepath -    path to project file. Uses in relative paths
     * @return
     * @throws IOException
     */
    private void loadEntities(Document doc, String homepath) throws IOException {
        List<org.jdom.Element> entities =  doc.getRootElement().getChildren("entity");
        for (Element e:entities){
            try{
                Entity entity = new EntityFactory().load(e);
                Controller.getInstance().addEntity(entity);
            }catch(Exception ez){
                ez.printStackTrace();
            }
        }
    }

    
     /**
     * loadTemplates meta entities from project definition
     * @param answer -      project which will be changed and filled with new templates
     * @param doc -         JDom Document which contain definition for entities
     * @param homepath -    path to project file. Uses in relative paths
     * @return
     * @throws IOException
     */
    private Project loadMetaEntities(Project answer, Document doc, String homepath) throws IOException {
        List<org.jdom.Element> entities =  doc.getRootElement().getChildren("metaentity");
        for (Element e:entities){
            try{
                MetaEntity entity = new MetaEntityFactory().load(e);
                answer.addMetaEntity(entity);
            }catch(Exception ez){
                log.error(ez);
            }
        }
        return answer;
    }

    /**
     * loadTemplates meta entities from project definition
     * @param doc -         JDom Document which contain definition for entities
     * @param homepath -    path to project file. Uses in relative paths
     * @return
     * @throws IOException
     */
    private void loadMetaEntities(Document doc, String homepath) throws IOException {
        List<org.jdom.Element> entities =  doc.getRootElement().getChildren("metaentity");
        for (Element e:entities){
            try{
                MetaEntity entity = new MetaEntityFactory().load(e);
                Controller.getInstance().addMetaEntity(entity);
            }catch(Exception ez){
                ez.printStackTrace();
            }
        }
    }

    /**
     * save current project data to xml. not data from entities but data about entities
     * not store entities data because goal was different
     * @param filename
     * @param project
     * @return
     */
    public boolean save(String filename, Project project, boolean useRelativePath) throws IOException, CircleException, UnsupportedEntityTypeException, UnsupportedMetaEntityTypeException {
        Document doc = new Document();
        Element root = new Element("project");
        doc.setRootElement(root);
        doc = saveTemplates(doc, project, useRelativePath);
        doc = saveMetaEntities(doc, project, useRelativePath);
        doc = saveEntities(doc,project,useRelativePath);
        new XMLOutputter().output(doc,new FileOutputStream(filename));
        return true;
    }

    private Document saveTemplates(Document doc, Project project, boolean useRelativePath){
        for (String tName:project.getTemplateNames()){
            Template template = project.getTemplateByName(tName);
            if (template.isPersistent()){
                Element etemplate = new Element(TEMPLATE_TAG);
                etemplate.setAttribute(TEMPLATE_ATTR_NAME,template.getName());
                etemplate.setAttribute(TEMPLATE_ATTR_FILE, useRelativePath?
                                                                template.getFilename().replace(project.getHomeDir(),"%HOME%")
                                                                :
                                                                template.getFilename());
                doc.getRootElement().addContent(etemplate);
            }
        }
        return doc;
    }

    private Document saveMetaEntities(Document doc, Project project, boolean useRelativePath) throws CircleException, UnsupportedMetaEntityTypeException {
        for (String entName:project.getMetaEntityNames()){
            if (project.getMetaEntityByName(entName).isPersistent()){
                MetaEntity current = project.getMetaEntityByName(entName);
                doc.getRootElement().addContent(current.getParser().save(current));
            }
        }
        return doc;
    }

    private Document saveEntities(Document doc, Project project, boolean useRelativePath) throws CircleException, UnsupportedEntityTypeException {
        List<String> entities = new ArrayList<String>();
        for (String entName:project.getEntityNames()){
            if (project.getEntityByName(entName).isPersistent()){
                entities.add(entName);
            }
        }
        List<String> saved = new ArrayList<String>();

        //check if entity has dependency.
        //if has pass, if no save and add to list
        //if after for count of entities dont changed - break because of circle dependency

        int lastSize = entities.size();
        while (entities.size()>0){
            lastSize = entities.size();
            for (int i=entities.size()-1;i>-1;i--){
                Entity current = project.getEntityByName(entities.get(i));
                //you can use .isEmpty but it can not work anywhere
                if (current.getDependencies()==null||current.getDependencies().size()==0){
                    doc.getRootElement().addContent(current.getParser().save(current));
                    saved.add(current.getName());
                    entities.remove(i);
                }else{
                    List<String> dependencies = current.getDependencies();
                    boolean resolved = true;
                    for (String dep:dependencies){
                        if (!saved.contains(dep)){
                            resolved = false;
                            break;
                        }
                    }
                    if (resolved){
                        doc.getRootElement().addContent(current.getParser().save(current));
                        saved.add(current.getName());
                        entities.remove(i);
                    }
                }
            }
            //if we dont handle any of entity that mean there is some circle
            if (entities.size()==lastSize&&entities.size()>0){
                throw new CircleException();
            }
        }
        return doc;
    }
}
